﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Diagnostics;
using System.Linq.Expressions;
using System.Data.Linq.Mapping;
using System.Collections;
using System.Reflection;
using System.Configuration;
using System.Data.Linq;
using TaobaoERP.Core.Common;
using TaobaoERP.Core.EventArgs;
using TaobaoERP.Core.Entity;
using System.Data.Common;

namespace TaobaoERP.Core.Repository
{
    public abstract class RepositoryBase 
    {
        /// <summary>
        /// Initializes a new instance of the <see cref="RepositoryBase"/> class.
        /// </summary>
        /// <param name="fileOrServerOrConnection">The file or server or connection.</param>
        /// <param name="isReadOnly">if set to <c>true</c> [is read only].</param>
        protected RepositoryBase() : this(new RepositoryContext())
        {
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="RepositoryBase"/> class.
        /// </summary>
        /// <param name="context">The context.</param>
        protected RepositoryBase(RepositoryContext context)
        {
            DataContext = context;
        }

        #region Properties

            /// <summary>
            /// Gets or sets the data context.
            /// </summary>
            /// <value>The data context.</value>
            private RepositoryContext DataContext { get; set; }

           

          

            #endregion

            #region Entity and metadata functions

            protected virtual TEntity GetEntity<TEntity>(Expression<Func<TEntity, bool>> queryFunc) where TEntity : class //Entity
            {

                //Debug.Assert(queryFunc != null, "expression passed in is null");

                var results = from entity in DataContext.GetTable<TEntity>()
                              select entity;


                results = results.Where(queryFunc);

                return results.FirstOrDefault();
            }

            protected virtual IQueryable<TEntity> GetEntities<TEntity>(Expression<Func<TEntity, bool>> queryFunc) where TEntity : class //Entity 
            {
                Debug.Assert(queryFunc != null, "expression passed in is null");

                var results = from entity in DataContext.GetTable<TEntity>()
                              select entity;

                results = results.Where(queryFunc);

                return results;
            }

            protected virtual IQueryable<TEntity> GetEntities<TEntity>() where TEntity : class //Entity 
            {
                var results = from entity in DataContext.GetTable<TEntity>()
                              select entity;

                return results;
            }

            protected MetaTable GetMetaTable(Type rowType)
            {
                return DataContext.Mapping.GetTable(rowType);
            }


            #endregion

            #region Delete and insert, update methods

            #region Delete entity

            protected void DeleteEntity<TEntity>(Expression<Func<TEntity, object>> fieldExpression, object value) where TEntity : class
            {

                MemberExpression memberExpression = null;

                if (fieldExpression != null)
                {
                    if (fieldExpression.Body is MemberExpression)
                    {
                        memberExpression = (MemberExpression)fieldExpression.Body;
                    }
                    else if (fieldExpression.Body is UnaryExpression && ((UnaryExpression)fieldExpression.Body).Operand is MemberExpression)
                    {
                        memberExpression = ((UnaryExpression)fieldExpression.Body).Operand as MemberExpression;
                    }
                }
                if (memberExpression == null)
                {
                    throw new NotSupportedException("The given expression is not supported.");
                }

                string fieldName = memberExpression.Member.Name;
                DeleteEntity(typeof(TEntity),
                            new List<KeyValuePair<string, object>>
                            {
                                new KeyValuePair<string, object>(fieldName,value)
                            }
                            );
            }

            protected void DeleteEntity(Type entityType, List<KeyValuePair<string, object>> criterias)
            {
                if (criterias == null || criterias.Count == 0)
                {
                    throw new ArgumentException("Some criterias must be set.", "criterias");
                }

                List<object> values = new List<object>();

                MetaTable table = GetMetaTable(entityType);
                string query = GetDeleteStatment(table);
                int criteriaId = 0;

                foreach (var criteria in criterias)
                {
                    if (criteriaId > 0)
                    {
                        query += " AND ";
                    }

                    if (criteria.Value is IEnumerable)
                    {
                        string ids = string.Empty;
                        query += " " + criteria.Key + " in (";
                        foreach (var item in (IEnumerable)criteria.Value)
                        {
                            if (ids.Length > 0)
                                ids += ",";

                            ids += GetParamTag(criteriaId);
                            values.Add(item);
                            criteriaId++;
                        }
                        if (string.IsNullOrEmpty(ids))
                            throw new ArgumentOutOfRangeException("criterias", criteria, string.Format("The criteria {0} has value as an IEnumerable which contains no elements. This is not supported", criteria.Key));
                        query += ids + ")";

                    }
                    else
                    {
                        query += " " + criteria.Key + " = {" + criteriaId + "}";
                        values.Add(criteria.Value);
                        criteriaId++;
                    }

                }

                DataContext.ExecuteCommand(query, values.ToArray());
            }

            protected void DeleteEntity(EntityBase entity)
            {
                Type entityType = entity.GetType();
                MetaTable table = GetMetaTable(entityType);

                List<object> arguments = new List<object>();
                var primaryKeyColumns = (from dm in table.RowType.PersistentDataMembers
                                         where dm.IsPrimaryKey
                                         select dm).ToList();

                if (primaryKeyColumns.Count == 0)
                {
                    throw new NotSupportedException("This method only support entities with primary keys.");
                }

                string query = GetDeleteStatment(table);

                foreach (var primaryField in primaryKeyColumns)
                {
                    if (arguments.Count != 0)
                    {
                        query += " AND ";
                    }

                    object val = entityType.GetProperty(primaryField.Name).GetValue(entity, null);
                    query += GetEqualStatment(primaryField.MappedName, arguments.Count);
                    arguments.Add(val);
                }

                DataContext.ExecuteCommand(query, arguments.ToArray());
            }

            private static string GetDeleteStatment(MetaTable table)
            {

                var isDeletedColumn = (from dm in table.RowType.PersistentDataMembers
                                       where dm.MappedName == "IsDeleted"
                                       select dm).SingleOrDefault();

                string query;
                if (isDeletedColumn == null)
                {
                    query = @"DELETE " + table.TableName + " WHERE ";
                }
                else
                {
                    query = @"UPDATE " + table.TableName + " SET IsDeleted=1 WHERE ";
                }
                return query;
            }

            private static string GetEqualStatment(string fieldName, int paramId)
            {
                return string.Format("{0} = {1}", fieldName, GetParamTag(paramId));

            }
            private static string GetParamTag(int paramId)
            {
                return "{" + paramId + "}";

            }
            #endregion

            #region Update Entity

            protected void UpdateEntity(EntityBase entity)
            {
                Dictionary<string, object> changes = entity.GetChanges();

                if (!(changes != null && changes.Count > 0))
                    return;

                if (entity.IsAlreadySaved())
                {
                    throw new InvalidOperationException("An instance of an entity can only be used to save data once.");
                }

                Type entityType = entity.GetType();
                MetaTable table = GetMetaTable(entityType);

                var persistentMembers = table.RowType.PersistentDataMembers;
                var primaryKeyColumns = (from dm in persistentMembers
                                         where dm.IsPrimaryKey
                                         select dm).ToList();

                if (primaryKeyColumns.Count == 0)
                    throw new NotSupportedException("This method only supports entities with primary keys.");

                List<object> arguments = new List<object>();
                StringBuilder builder = new StringBuilder();

                foreach (KeyValuePair<string, object> change in changes)
                {
                    if ((persistentMembers.Where(m => m.Name == change.Key && !m.IsAssociation)).Count() != 1)
                        continue;

                    if (arguments.Count != 0)
                        builder.Append(", ");

                    if (change.Value != null)
                    {
                        builder.Append(change.Key + " = {" + arguments.Count + "}");
                        arguments.Add(change.Value);
                    }
                    else
                    {
                        // DataContext.ExecuteCommand() does not accept null values in the arguments list. (Differs from the description in MSDN doc)
                        builder.Append(change.Key + " = NULL");
                    }
                }

                if (builder.Length == 0)
                    return;

                builder.Insert(0, "UPDATE " + table.TableName + " SET ");

                builder.Append(" WHERE ");
                bool firstPrimaryKey = true;

                foreach (var primaryField in primaryKeyColumns)
                {
                    if (firstPrimaryKey)
                        firstPrimaryKey = false;
                    else
                        builder.Append(" AND ");

                    object val = entityType.GetProperty(primaryField.Name).GetValue(entity, null);
                    builder.Append(GetEqualStatment(primaryField.MappedName, arguments.Count));
                    arguments.Add(val);
                }

                entity.MarkEntitySaved();

                try
                {
                    DataContext.ExecuteCommand(builder.ToString(), arguments.ToArray());
                }
                catch (Exception ex)
                {
                    Debug.WriteLine(ex);
                    throw;
                }
            }

            #endregion

            #region Insert Entity

            protected void InsertEntity(EntityBase entity)
            {
                if (entity == null)
                    throw new ArgumentException("The database entity can not be null.");

                Type entityType = entity.GetType();
                MetaTable table = GetMetaTable(entityType);
                MetaDataMember identityDatamember = table.RowType.DBGeneratedIdentityMember;

                List<object> arguments = new List<object>();
                StringBuilder fieldbuilder = new StringBuilder();
                StringBuilder valuebuilder = new StringBuilder();

                fieldbuilder.Append("INSERT INTO " + table.TableName + " (");

                foreach (var member in table.RowType.PersistentDataMembers)
                {
                    if (!member.IsAssociation && !member.IsDbGenerated)
                    {
                        object value = entityType.GetProperty(member.Name).GetValue(entity, null);
                        if (value != null)
                        {
                            if (arguments.Count != 0)
                            {
                                fieldbuilder.Append(", ");
                                valuebuilder.Append(", ");
                            }

                            fieldbuilder.Append(member.MappedName);
                            valuebuilder.Append("{" + arguments.Count + "}");

                            arguments.Add(value);

                        }
                    }
                }

                fieldbuilder.Append(") Values (");
                fieldbuilder.Append(valuebuilder.ToString());
                fieldbuilder.Append(");");

                if (identityDatamember != null)
                {
                    fieldbuilder.AppendFormat("SELECT * From {1} Where {0} = (CONVERT(Int,SCOPE_IDENTITY())) ", identityDatamember.MappedName, table.TableName);
                    IEnumerable result = DataContext.ExecuteQuery(entityType, fieldbuilder.ToString(), arguments.ToArray());

                    foreach (var item in result)
                    {
                        PropertyInfo property = entityType.GetProperty(identityDatamember.Name);
                        object pk = property.GetValue(item, null);
                        property.SetValue(entity, pk, null);
                    }
                }
                else
                {
                    DataContext.ExecuteCommand(fieldbuilder.ToString(), arguments.ToArray());
                }
            }

            #endregion

            #endregion

            #region Stored Procedures

            protected class StoredProcedureArgument
            {
                public string Name { get; set; }

                public object Value { get; set; }

                public StoredProcedureArgument(string name, object value)
                {
                    this.Name = name;
                    this.Value = value;
                }
            }

            protected IEnumerable<TEntity> ExecuteStoredProcedure<TEntity>(string storedProcedure, params  StoredProcedureArgument[] arguments) where TEntity : EntityBase
            {
                Debug.Assert(!string.IsNullOrEmpty(storedProcedure), "procedure name was not found");

                if (arguments == null || arguments.Length == 0)
                {
                    return this.DataContext.ExecuteQuery<TEntity>(storedProcedure);
                }

                StringBuilder sql = BuildSql(storedProcedure, arguments);

                object[] argList = (from argument in arguments select argument.Value).ToArray();

                return this.DataContext.ExecuteQuery<TEntity>(sql.ToString(), argList);
            }

            protected void ExecuteStoredProcedure(string storedProcedure, params  StoredProcedureArgument[] arguments)
            {
                Debug.Assert(!string.IsNullOrEmpty(storedProcedure), "procedure name was not found");

                if (arguments == null || arguments.Length == 0)
                {
                    this.DataContext.ExecuteCommand(storedProcedure);
                    return;
                }

                StringBuilder sql = BuildSql(storedProcedure, arguments);

                object[] argList = (from argument in arguments select argument.Value).ToArray();

                this.DataContext.ExecuteCommand(sql.ToString(), argList);

            }

            private static StringBuilder BuildSql(string storedProcedure, IEnumerable<StoredProcedureArgument> arguments)
            {
                StringBuilder sql = new StringBuilder();
                sql.Append("Exec ");
                sql.Append(storedProcedure);
                sql.Append(" ");
                int index = 0;

                foreach (var argument in arguments)
                {
                    if (index != 0)
                    {
                        sql.Append(", ");
                    }
                    sql.Append(argument.Name + " = {" + index + "}");

                    index++;
                }
                return sql;
            }

            #endregion

            #region Static help methods

            protected static bool IsValueDefined(DateTime? value)
            {
                return (value != null && value != DateTime.MinValue && value != DateTime.MaxValue);
            }

            #endregion




            #region Events

            /// <summary>
            /// Occurs after data saved
            /// </summary>
            public event EventHandler<SavedEventArgs> AfterSaved;

            /// <summary>
            /// Occurs before saved
            /// </summary>
            public event EventHandler<SavedEventArgs> BeforeSaved;

            /// <summary>
            /// Occurs when save failed
            /// </summary>
            public event EventHandler<FailedEventArgs> SaveFailed;

            #endregion

            #region Virtual Methods

            /// <summary>
            /// Called after data saved
            /// </summary>
            /// <param name="sender">The sender.</param>
            /// <param name="action">The action.</param>
            protected virtual void OnAfterSaved(Object sender, SaveAction action)
            {
                EventHandler<SavedEventArgs> tempHandler = AfterSaved;
                if (tempHandler != null)
                {
                    tempHandler(sender, new SavedEventArgs(sender, action));
                }
            }

            /// <summary>
            /// Called before saved
            /// </summary>
            /// <param name="sender">The sender.</param>
            /// <param name="action">The action.</param>
            protected virtual void OnBeforeSaved(Object sender, SaveAction action)
            {
                EventHandler<SavedEventArgs> tempHandler = BeforeSaved;
                if (tempHandler != null)
                {
                    tempHandler(sender, new SavedEventArgs(sender, action));
                }
            }

            /// <summary>
            /// Called when save failed
            /// </summary>
            /// <param name="sender">The sender.</param>
            /// <param name="exception">The exception.</param>
            protected virtual void OnSaveFailed(Object sender, Exception exception)
            {
                EventHandler<FailedEventArgs> tempHandler = SaveFailed;
                if (tempHandler != null)
                {
                    tempHandler(sender, new FailedEventArgs(sender, exception));
                }
            }

            #endregion

            
    }
}
